//============================================================================
// Name        : OpenGl.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <gl/glut.h>
#include <iostream>
#include <vector>
#include <math.h>
#include <windows.h>
#include "glm/glm.h"
#include "KeyboardHandler.h"
#include "MouseClickHandler.h"
#include "md2/imageloader.h"
#include "md2/md2model.h"
#include "map.h"

using namespace std;

#define window_width  1000
#define window_height 750
#define BUFSIZE 512
enum {
  DL_LIGHT_SPHERE = 1,
  DL_BIG_SPHERE = 2,
  DL_ICO = 3
};

GLfloat angleZX = 0;
GLfloat angleZY = 0;
//GLfloat angleZ = 0;
GLfloat speed = 0.2;
GLfloat rSpeed = 0.5;
GLfloat aimX = 0;
GLfloat aimY = 0;
GLfloat aimZ = -1;
GLfloat eyeX = 0;
GLfloat eyeY = 1.6;
GLfloat eyeZ = 5;
GLfloat upX = 0;
GLfloat upY = 1;
GLfloat upZ = 0;

GLfloat manX = 0;
GLfloat manY = 0;
GLfloat manZ = 0;
GLfloat manAngle = 90;
GLfloat manSpeed = 0.03;

bool died = false;

KeyboardHandler keyboardHandler;
MouseClickHandler mouseHandler;
Map* map = new Map();

enum MODE {
	NORMAL,
	SELECTION
};
MODE mode = NORMAL;

GLfloat lightPos[8][3];
GLfloat fanPos[4][3];

GLfloat lightColor[] = {0.5f, 0.5f, 0.5f, 1.0f};

GLboolean light_enable[8];
GLboolean fan_running[4];

GLfloat scale = 1;

void init_lights();
void draw_light();
void draw_floor();
void processHits(GLint hits, GLuint buffer[]);
void draw_table_row();
void draw_all_tables();
void setup_light();
void setup_fan();
void draw_switch_fan();
void draw_man();

GLMmodel *model_room;
GLMmodel *model_table;
GLMmodel *model_switch_light;
GLMmodel *model_switch_fan;
GLMmodel *model_light_on;
GLMmodel *model_light_off;
MD2Model* model_fan[4];
MD2Model* model_tall_guy;



//GLfloat tx = 0, ty = 0, tz = 0;

// Main loop
void display() {
	// Clear color (screen)
	// And depth (used internally to block obstructed objects)
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// Load identity matrix
	glLoadIdentity();
	gluLookAt(eyeX, eyeY, eyeZ, eyeX + aimX, eyeY + aimY, eyeZ + aimZ, upX, upY, upZ);

	mouseHandler.clear();

	init_lights();

    draw_light();
    draw_switch_fan();

	glColor3f(1, 1, 1);

	//glScalef(0.01, 0.01, 0.01);
	//glDisable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);
	glPushMatrix();
	glTranslatef(0, 0, 0);
	glScalef(scale * 0.01, scale * 0.01, scale * 0.01);
	glmDraw(model_room,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
	glPopMatrix();

	for (int i = 0; i < 8; i++) {
		glPushMatrix();
		glTranslatef(lightPos[i][0], lightPos[i][1], lightPos[i][2]);
		glScalef(scale * 0.01, scale * 0.01, scale * 0.01);
		if (light_enable[i])
			glmDraw(model_light_on,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
		else
			glmDraw(model_light_off,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
		glPopMatrix();
	}

	for (int i = 0; i < 4; i++) {
		if (model_fan != NULL) {
			glPushMatrix();
			glDisable(GL_TEXTURE_2D);
			glTranslatef(fanPos[i][0], fanPos[i][1], fanPos[i][2]);
			glScalef(scale * 0.03, scale * 0.03, scale * 0.03);
			glRotatef(-90, 1, 0, 0);
			glColor3f(72.0 / 256, 155.0 / 256, 167.0 / 256);
			model_fan[i]->draw();
			glEnable(GL_TEXTURE_2D);
			glPopMatrix();
		}
	}

	draw_man();

	//glPushMatrix();


	glEnable(GL_LIGHTING);
	//glutSolidSphere(1.0, 40, 16);

	draw_all_tables();

	draw_floor();

/*	if (model_fan != NULL) {
		//glDisable(GL_LIGHTING);
		glDisable(GL_TEXTURE_2D);
		glPushMatrix();
		glTranslatef(0, 3.8, 0);
		glScalef(scale * 0.03, scale * 0.03, scale * 0.03);
		glRotatef(-90, 1, 0, 0);
		glColor3f(72.0/256, 155.0/256, 167.0/256);
		model_fan->draw();
		glPopMatrix();
		glEnable(GL_TEXTURE_2D);
		//glEnable(GL_LIGHTING);
	}*/

	glutSwapBuffers();

}

void draw_table_row() {
	for (int i = 0; i < 10; i++) {
		glPushMatrix();
		glTranslatef(0, 0, 0.9 * i * scale);

		glScalef(scale * 0.01, scale * 0.01, scale * 0.01);
		glmDraw(model_table,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
		glPopMatrix();
	}
}

void draw_all_tables() {
	glPushMatrix();
	glTranslatef(0, 0.3 * scale, 0);

	glPushMatrix();
	glTranslatef(2.8 * scale, 0, -2.85 * scale);
	draw_table_row();
	glPopMatrix();

	glPushMatrix();
	glTranslatef(1.4 * scale, 0, -2.85 * scale);
	draw_table_row();
	glPopMatrix();


	glPushMatrix();
	glTranslatef(-1.2 * scale, 0, -2.85 * scale);
	draw_table_row();
	glPopMatrix();

	//glPopMatrix();


	glPushMatrix();
	glTranslatef(-2.6 * scale, 0, -2.85 * scale);
	draw_table_row();
	glPopMatrix();

	glPopMatrix();
}

void init_lights() {
	GLfloat mat_shininess[] = { 100.0 };
	GLfloat mat_specular[] = { -2.0, 2.0, 0.0, 1.0 };
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
	glEnable(GL_LIGHTING);
    glShadeModel (GL_SMOOTH);
    for (int i = 0; i < 8; i++) {
    	if (light_enable[i]) {
    	    glEnable(GL_LIGHT0 + i);
        	glLightf(GL_LIGHT0 + i, GL_LINEAR_ATTENUATION, 10);
    	    glLightfv(GL_LIGHT0 + i, GL_DIFFUSE, lightColor);
    	    //glLightfv(GL_LIGHT0, GL_SHININESS, mat_shininess);
    	    glLightfv(GL_LIGHT0 + i, GL_POSITION, lightPos[i]);
    	} else {
    	    glDisable(GL_LIGHT0 + i);
    	}
    }

	GLfloat ambientColor[] = {1, 1, 1, 0.0f}; //Color(0.2, 0.2, 0.2)
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientColor);
}

void draw_man() {
	GLboolean lighting_enable = glIsEnabled(GL_LIGHTING);
	int color[3];
	glPushMatrix();
	glTranslatef(0, 1.6, 0);
	glTranslatef(manX, manY, manZ);
	glScalef(scale * 0.07, scale * 0.07, scale * 0.07);
	glRotatef(-90, 0, 0, 1);
	glRotatef(manAngle, 1, 0, 0);
	if (!died) {
		//model_tall_guy->draw();
		if (mode == SELECTION) {
			glDisable(GL_LIGHTING);
			mouseHandler.addObject(20, color);
			glColor3f((float)color[0]/255, (float)color[1]/255, (float)color[2]/255);
			model_tall_guy->draw(true);
			lighting_enable ? glEnable(GL_LIGHTING) : glDisable(GL_LIGHTING);
		} else {
			model_tall_guy->draw();
		}
	} else {
		glRotatef(90, 0, 0, 1);
		glScalef(1 / scale / 0.07, 1 / scale / 0.07, 1/ scale / 0.07);
		glTranslatef(0, -1.2, 0);
		glScalef(scale * 0.07, scale * 0.07, scale * 0.07);
		model_tall_guy->draw();
	}
	glPopMatrix();
}

void draw_switch_fan() {
	glPushMatrix();
	GLboolean lighting_enable = glIsEnabled(GL_LIGHTING);
	int color[3];

	GLfloat siwtch_fan_pos[4][3]= {{-2.95, 1.75, -6},
									{-2.88, 1.75, -6},
									{-2.95, 1.75, 5.95},
									{-2.88, 1.75, 5.95}};

	for (int i = 0; i < 4; i++) {
		glPushMatrix();

		glTranslatef(siwtch_fan_pos[i][0], siwtch_fan_pos[i][1], siwtch_fan_pos[i][2]);
		glScalef(scale * 0.01, scale * 0.01, scale * 0.01);
		if (i == 2 || i == 3) glRotatef(180, 0 , 1, 0);
		if (mode == SELECTION) {
			glDisable(GL_LIGHTING);
			mouseHandler.addObject(i + 3, color);
			glColor3f((float)color[0]/255, (float)color[1]/255, (float)color[2]/255);
			glmDraw(model_switch_fan,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
			lighting_enable ? glEnable(GL_LIGHTING) : glDisable(GL_LIGHTING);
		} else {
			glmDraw(model_switch_fan,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
		}
		glPopMatrix();
	}

	glPopMatrix();
}

void draw_light() {
	glPushMatrix();
	GLboolean lighting_enable = glIsEnabled(GL_LIGHTING);
	int color[3];

	glPushMatrix();
	glTranslatef(-3.05, 1.8, -6);
	glScalef(scale * 0.01, scale * 0.01, scale * 0.01);

	if (mode == SELECTION) {
		glDisable(GL_LIGHTING);
		mouseHandler.addObject(1, color);
		glColor3f((float)color[0]/255, (float)color[1]/255, (float)color[2]/255);
		glmDraw(model_switch_light,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
		lighting_enable ? glEnable(GL_LIGHTING) : glDisable(GL_LIGHTING);
	} else {
		glmDraw(model_switch_light,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
	}
	glPopMatrix();

	glPushMatrix();
	glTranslatef(-3.05, 1.8, 5.95);
	glScalef(scale * 0.01, scale * 0.01, scale * 0.01);

	if (mode == SELECTION) {
		glDisable(GL_LIGHTING);
		mouseHandler.addObject(2, color);
		glColor3f((float)color[0]/255, (float)color[1]/255, (float)color[2]/255);
		glmDraw(model_switch_light,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
		lighting_enable ? glEnable(GL_LIGHTING) : glDisable(GL_LIGHTING);
	} else {
		glmDraw(model_switch_light,GLM_SMOOTH|GLM_TEXTURE|GLM_MATERIAL);
	}
	glPopMatrix();

/*	if (mode == SELECTION) {
		mouseHandler.addObject(2, color);
		glColor3f((float)color[0]/255, (float)color[1]/255, (float)color[2]/255);
	} else {
		glColor3fv(lightColor1);
	}
	glPushMatrix();
	glTranslatef(lightPos1[0], lightPos1[1], lightPos1[2]);
	glutSolidSphere(0.2, 10, 4);
	glPopMatrix();

	//glColor3fv(lightColor2);
	glPushMatrix();
	glTranslatef(lightPos2[0], lightPos2[1], lightPos2[2]);
	//glCallList(DL_LIGHT_SPHERE);
	// glutSolidSphere(0.2, 10, 4);
	glPopMatrix();
	if (light_enable) {
		glEnable(GL_LIGHTING);
	} else {
		glDisable(GL_LIGHTING);
	}*/

	glPopMatrix();
}


void draw_floor() {
	glDisable (GL_LIGHTING);
	glColor3f(0, 0, 1);
	float l = 20;
	float d = 0.5;
	for (float i = -l; i <= l; i += d) {
		glBegin(GL_LINES);
			glVertex3f(i, 0, -l);
			glVertex3f(i, 0, l);
		glEnd();

	}
	for (float i = -l; i <= l; i += d) {
		glBegin(GL_LINES);
			glVertex3f(-l, 0, i);
			glVertex3f(l, 0, i);
		glEnd();

	}
	glEnable(GL_LIGHTING);
}

// Initialze OpenGL perspective matrix
void GL_Setup(int width, int height) {
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glEnable(GL_DEPTH_TEST);
	gluPerspective(45, (float) width / height, .1, 100);
	glMatrixMode(GL_MODELVIEW);
	setup_light();
	setup_fan();

	model_room=glmReadOBJ("phong.obj");
	//model_room=glmReadOBJ("final/final.obj");
	model_table=glmReadOBJ("ban-hoc.obj");
	model_switch_light=glmReadOBJ("cong-tac.obj");
	model_switch_fan=glmReadOBJ("hop-quat.obj");
	model_light_on = glmReadOBJ("den-bat.obj");
	model_light_off = glmReadOBJ("den-tat.obj");
	for (int i = 0; i < 4; i++)
	{
		model_fan[i] = MD2Model::load("quat-tran.md2");
		if (model_fan[i] != NULL) {
			model_fan[i]->setAnimation("run");
		}
	}
	model_tall_guy = MD2Model::load("tallguy.md2");
	model_tall_guy->setAnimation("run");

	map->loadData("map.txt");
    //glmUnitize(model);
    //glmFacetNormals(model);
	//glmVertexNormals(model, 90.0);

}

void setup_fan() {
	for (int i = 0; i < 4; i++) {
		fan_running[i] = false;
	}
	fanPos[3][0] = 2;
	fanPos[3][1] = 3.8;
	fanPos[3][2] = 3;

	fanPos[1][0] = 2;
	fanPos[1][1] = 3.8;
	fanPos[1][2] = -2.5;

	fanPos[0][0] = -2;
	fanPos[0][1] = 3.8;
	fanPos[0][2] = -2.5;

	fanPos[2][0] = -2;
	fanPos[2][1] = 3.8;
	fanPos[2][2] = 3;
}

void setup_light() {
	lightPos[0][0] = 2;
	lightPos[0][1] = 3.9;
	lightPos[0][2] = -3.8;
	for (int i = 1; i < 4; i++) {
		lightPos[i][0] = lightPos[0][0];
		lightPos[i][1] = lightPos[0][1];
		lightPos[i][2] = lightPos[i - 1][2] + 2.7;
	}

	for (int i = 4; i < 8; i++) {
		lightPos[i][0] = -2;
		lightPos[i][1] = 3.9;
		lightPos[i][2] = lightPos[i - 4][2];
	}
}

void keyboard(unsigned char key, int x, int y)
{
	switch (key) {
	case 27:
		exit(0);
		break;
		/*	case 'a':
		tx -= 0.2;
		break;
	case 'd':
		tx += 0.2;
		break;
	case 'w':
		tz -= 0.2;
		break;
	case 's':
		tz += 0.2;
		break;
	case 'r':
		ty += 0.2;
		break;
	case 'f':
		ty -= 0.2;
		break;
	case 47:
		break;*/
	}
	//cout << (int)key << endl; fflush(stdout);
	keyboardHandler.event(key, x, y, KeyboardHandler::KEY_DOWN);
	glutPostRedisplay();
	//cout << tx << " " << ty << " " << tz << endl;
}

void keyboard_u(unsigned char key, int x, int y) {
	keyboardHandler.event(key, x, y, KeyboardHandler::KEY_UP);
	//cout << (int)key << endl; fflush(stdout);
}

void keyboard_s(int key, int x, int y)
{
	keyboardHandler.event(key, x, y, KeyboardHandler::KEY_DOWN);
}

void keyboard_su(int key, int x, int y) {
	keyboardHandler.event(key, x, y, KeyboardHandler::KEY_UP);
}

void move(int key) {
	GLfloat t = 0;
	t = sqrt(aimX * aimX + aimY * aimY + aimZ * aimZ);
	GLfloat nX, nZ;
	nX = eyeX;
	nZ = eyeZ;
	switch (key)
	{
	case GLUT_KEY_LEFT:
		//eyeX -= 0.2;
		nX = eyeX - cos(angleZX) * speed;
		nZ = eyeZ - sin(angleZX) * speed;
		break;
	case GLUT_KEY_RIGHT:
		//eyeX += 0.2;
		nX = eyeX + cos(angleZX) * speed;
		nZ = eyeZ + sin(angleZX) * speed;
		break;
	case GLUT_KEY_UP:
		//angleY -= speed;
		nX = eyeX + aimX * speed / t;
		//eyeY += aimY * speed / t;
		nZ = eyeZ + aimZ * speed / t;
		//eyeZ -= 0.2;
		break;
	case GLUT_KEY_DOWN:
		//angleY += speed;
		nX = eyeX - aimX * speed / t;
		//eyeY -= aimY * speed / t;
		nZ = eyeZ - aimZ * speed / t;
		break;
	default:
		return;
	}
	float height = map->getHeight(nX, nZ);
	if (height > 60) return;
	cout << height << endl; fflush(stdout);
	if (height - (eyeY - 1.6) < 50) {
		if (height - (eyeY - 1.6) < 0.5) {
			eyeX = nX;
			eyeZ = nZ;
			eyeY = height + 1.6;
		} else if (height - (eyeY - 1.6) > 0.5 && keyboardHandler.isPressed(47)) {
			eyeX = nX;
			eyeZ = nZ;
			eyeY = height + 1.6;
		}
	}
	//cout << eyeX << " " << eyeY << " " << eyeZ << endl;
	//fflush(stdout);
}

void move_man(int key) {
	GLfloat t = 0;
	t = sqrt(aimX * aimX + aimY * aimY + aimZ * aimZ);
	GLfloat nX, nZ;
	nX = manX;
	nZ = manZ;
	switch (key)
	{
	case 'a':
		manAngle -= 5;
		return;
	case 's':
		manAngle += 5;
		return;
	case 'w':
		nX = manX - sin(manAngle * 3.14 / 180) * manSpeed;
		nZ = manZ + cos(manAngle * 3.14 / 180) * manSpeed;
		break;
	default:
		return;
	}

	float height = map->getHeight(nX, nZ);
	if (height - manY < 50) {
		if (height - manY < 1) {
			manX = nX;
			manZ = nZ;
			manY = height;
			model_tall_guy->advance(0.035f);
		} else if (height - manY > 1 && keyboardHandler.isPressed(47)) {
			manX = nX;
			manZ = nZ;
			manY = height;
			model_tall_guy->advance(0.035f);
		}
	}
}

void mouse(int x, int y) {
	//cout << x << " " << y << endl;
	if (x - window_width / 2 > 5) {
		angleZX += 0.02;
	}
	if (x - window_width / 2 < -5) {
		angleZX -= 0.02;
	}
	if (y - window_height / 2 < -5 && angleZY < 1) {
		angleZY += 0.02;
	}
	if (y - window_height / 2 > 5 && angleZY > -1) {
		angleZY -= 0.02;
	}

	aimY = sin(angleZY);
	aimX = cos(angleZY) * sin(angleZX);
	aimZ = -cos(angleZY) * cos(angleZX);
	glutPostRedisplay();

	if (abs(x - window_width / 2) > 5 || abs(y - window_height / 2) > 5)
		glutWarpPointer(window_width / 2, window_height / 2);
	//cout << aimX << " " << aimY << " " << aimZ << endl;
}

void mouseClick(int button, int state, int x, int y) {
	if (state != GLUT_DOWN) return;
	mode = SELECTION;

    // turn off texturing, lighting and fog
    glDisable(GL_TEXTURE_2D);
	glDisable(GL_FOG);
	glDisable(GL_LIGHTING);

	display();

	// get color information from frame buffer
	unsigned char pixel[3];

	GLint viewport[4];
	glGetIntegerv(GL_VIEWPORT, viewport);

	glReadPixels(x, viewport[3] - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixel);
	//cout << "click " << (int) pixel[0] << " " << (int)pixel[1] << endl;

	int id = -1;
	id = mouseHandler.getObject(pixel);
	if (id == 1) {
		light_enable[0] = !light_enable[0];
		light_enable[1] = !light_enable[1];
		light_enable[4] = !light_enable[4];
		light_enable[5] = !light_enable[5];
	} else if (id == 2) {
		light_enable[2] = !light_enable[2];
		light_enable[3] = !light_enable[3];
		light_enable[6] = !light_enable[6];
		light_enable[7] = !light_enable[7];
		//PlaySound("m3p1.wav", NULL, SND_ASYNC);
	} else {
		if (id >= 3 && id <= 6) {
			fan_running[id - 3] = !fan_running[id - 3];
		}
	}
	if (id == 20) {
		died = true;
		PlaySound("m3p1.wav", NULL, SND_ASYNC);
		PlaySound("m3p1.wav", NULL, SND_ASYNC);
	}
	//cout <<id;
	fflush(stdout);
	glEnable(GL_TEXTURE_2D);
	//glEnable(GL_FOG);
	glEnable(GL_LIGHTING);
	mode = NORMAL;
}

void update(int value) {
	vector<int> ks;
	keyboardHandler.getKeysPressed(ks);
	int size = ks.size();
	if (size != 0) {
		for (int i = 0; i < size; i++) {
			move(ks[i]);
			if (!died) move_man(ks[i]);
		}
	}
	//model_tall_guy->advance(0.07f);
	for (int i = 0; i < 4; i++) {
		if (model_fan[i] != NULL && fan_running[i]) {
			model_fan[i]->advance(0.05f);
		}
	}
	glutPostRedisplay(); //Tell GLUT that the scene has changed
    glutTimerFunc(50, update, 0);
}

// Initialize GLUT and start main loop
int main(int argc, char** argv) {
	fflush(stdout);
	glutInit(&argc, argv);
	glutInitWindowSize(window_width, window_height);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
	glutCreateWindow("GLUT Example!!!");
	//glutIdleFunc(main_loop_function);
	GL_Setup(window_width, window_height);
	glutIgnoreKeyRepeat(1);
	glutKeyboardFunc(keyboard);
	glutKeyboardUpFunc(keyboard_u);
	glutSpecialFunc(keyboard_s);
	glutSpecialUpFunc(keyboard_su);
	glutTimerFunc(25, update, 0);
	glutSetCursor(GLUT_CURSOR_CROSSHAIR);
	//glutMouseFunc(mouse);
	glutPassiveMotionFunc(mouse);
	glutMouseFunc(mouseClick);
	glutDisplayFunc(display);
	glutMainLoop();
}
